﻿using System;
using System.Diagnostics;
using System.Drawing;
using System.Net;

using System.Text;
using System.Windows.Forms;

using Nextensions.WinApi;
using Nextensions.WinApi.Input;
using MouseEvent=Nextensions.WinApi.Input.MouseEvent;

namespace MouseAndCat.Model
{
    public class Master
    {
        public enum XEntry
        {
            Left = 1,
            Right = 2
        }

        private delegate void VoidDelegate();
        private Messenger _messenger;
        private MouseHook _mouseHook = new MouseHook();
        private KeyboardHook _keyboardHook = new KeyboardHook();
        private TransparentForm _transparentForm;
        
        private bool _controlling = false;
        private short _screenWidth;
        private short _screenHeight;
        private XEntry _reEntryX;
        private Form _invisibleWindow;

        private bool _debugKey1Down;
        private bool _debugKey2Down;

        public Master(IPAddress ip, int port, short screenWidth, short screenHeight, IntPtr windowHandle)
        {
             _transparentForm = new TransparentForm();
            _messenger = new Messenger(ip, port);
            _messenger.MouseLeave += MouseLeaveCallback;
            _screenWidth = screenWidth;
            _screenHeight = screenHeight;

            _mouseHook.MouseEventOccured += MouseHook_MouseEventOccured;
            _keyboardHook.KeyboardEventOccured += KeyboardHook_KeyboardEventOccured;
        }

        public void Start()
        {
            _mouseHook.Start();
            _keyboardHook.Start();
        }

        private void MouseHook_MouseEventOccured(MouseEvent eventType, uint timestamp, short x, short y, short wheelDelta)
        {
            if (!_controlling)
            {
                Debug.WriteLine("Mouse moved: {0}, {1}", x, y);
                //Debug.WriteLine(String.Format("{3} | Mouse event occured: {0}: @{1},{2}", eventType, x, y, timestamp));

                if (x < 0 || x > _screenWidth)
                {
                    Debug.WriteLine("Mouse out of bounds: " + x);

                    if (!StartControlling(timestamp, x, y))
                        return;
                }
                else
                {
                    return;
                }
            }

            
            Event eventType2 = Event.None;

            switch (eventType)
            {
                case MouseEvent.Move:

                    Debug.WriteLine("Mouse moved: {0}, {1}", x, y);
                    if (x > 100 || y > 100)
                        return;
                    eventType2 = Event.MouseMove;
                    break;

                case MouseEvent.LDown:
                    eventType2 = Event.MouseDown;
                    break;

                case MouseEvent.LUp:
                    eventType2 = Event.MouseUp;
                    break;

                case MouseEvent.LDoubleClick:

                    eventType2 = Event.LeftMouseDoubleClick;
                    break;

                case MouseEvent.RDown:

                    eventType2 = Event.RightMouseDown;
                    break;

                case MouseEvent.RUp:

                    eventType2 = Event.RightMouseUp;
                    break;

                case MouseEvent.Wheel:

                    eventType2 = Event.MouseWheel;
                    break;
            }

            SendMouseEvent(eventType2, timestamp, new Point(x, y), wheelDelta);
        }

        private void KeyboardHook_KeyboardEventOccured(KeyboardEvent eventType, uint timestamp,  byte key)
        {
            if (!_controlling)
                return;

            if (eventType == KeyboardEvent.KeyDown)
            {
                if (key == (byte)Keys.LControlKey)
                {
                    //Debug.WriteLine("Left CTRL down");
                    _debugKey1Down = true;
                }
                else if (key == (byte)Keys.RControlKey)
                {
                    //Debug.WriteLine("Right CTRL down");
                    _debugKey2Down = true;
                }
            }

            if (_debugKey1Down && _debugKey2Down)
            {
                Debug.WriteLine("Debug key combo detected.");
                //_reEntryX = (short)(_screenWidth / 2);
                StopControlling(333);
            }

            Event eventType2 = Event.None;

            switch (eventType)
            {
                case KeyboardEvent.KeyDown:

                    eventType2 = Event.KeyDown;
                    break;

                case KeyboardEvent.KeyUp:

                    if (key == (byte)Keys.LMenu || key == (byte)Keys.RMenu) // The Down event for the ALT keys is not detected (probably because its the accessibility/accelerator key) so we send it manually.
                    {
                        Debug.WriteLine("simulating alt down event");
                        SendKeyboardEvent(Event.KeyDown, timestamp, key);
                    }
                    eventType2 = Event.KeyUp;
                    break;
            }

            Debug.WriteLine(String.Format("Keyboard event detected: {0}, Key: {1} ({2}) @{3}", eventType2, key, (Keys)key, timestamp));
            SendKeyboardEvent(eventType2, timestamp, key);
        }
        
        private void StopControlling(short y)
        {
            if (_transparentForm.InvokeRequired)
                _transparentForm.Invoke(new VoidDelegate(HideTransparentForm));
            else
                HideTransparentForm();

            Debug.WriteLine(String.Format("Stopping controlling: {0}, {1}", _reEntryX, y));
            _mouseHook.CallNextHook = true;
            _keyboardHook.CallNextHook = true;

            y = (short)(((float)y / 100) * _screenHeight);
            Nextensions.WinApi.Input.Cursor.SetPosition(new Point(_reEntryX == XEntry.Left ? 0 : _screenWidth, y));

            _controlling = false;
            _debugKey1Down = false;
            _debugKey2Down = false;
        }

        private void HideTransparentForm()
        {
            _transparentForm.Hide(); 
        }

        private bool StartControlling(uint timestamp, short x, short y)
        {
            _controlling = true;
            _mouseHook.CallNextHook = false;
            _keyboardHook.CallNextHook = false;

            _messenger.ListenOnce();
            //int xEntry = x <= 0 ? (int)XEntry.Right : (int)XEntry.Left; 
            // Set the X entry to the side where the mouse will enter the slave (not where the mouse leaves the master). So, if the mouse leaves us from the left, we send a 'right' mouse entry to the slave.
            XEntry xEntry = x <= 0 ? XEntry.Right : XEntry.Left;
            Point point = new Point((int)xEntry, Convert.ToInt32(((float)y / (float)_screenHeight) * 100));
            SendMouseEvent(Event.MouseEnter, timestamp, point, 0); 
            
            _reEntryX = x <= 0 ?
                XEntry.Left : 
                XEntry.Right;
            
            Debug.WriteLine(String.Format("Sent mouse enter event: {0}, {1}", x, y));

            _transparentForm.Show();
            _transparentForm.Location = new Point(0, 0);
            _transparentForm.Size = new Size(1, 1);
            Nextensions.WinApi.Input.Cursor.SetPosition(new Point(0, 0));
            return true;
        }

        private void MouseLeaveCallback(short y)
        {
            StopControlling(y);
        }

        /*private void _invisibleWindow_MouseEnter(object sender, EventArgs e)
        {
            //Nextensions.WinApi.Input.Cursor.Hide();
        }

        private void _invisibleWindow_MouseCaptureChanged(object sender, EventArgs e)
        {
           // Nextensions.WinApi.Input.Cursor.Hide();
        }

        private void HideInvisibleWindow()
        {
            if (_invisibleWindow.InvokeRequired)
            {
                _invisibleWindow.Invoke(new VoidDelegate(HideInvisibleWindow));
                return;
            }

            _invisibleWindow.Hide();
            _invisibleWindow = null;
        }*/

        public void SendMouseEvent(Event eventType, uint timestamp, Point position, short wheelDelta)
        {
            
            //StringBuilder bytesString = new StringBuilder(255);
            //foreach (byte b in data)
            //   bytesString.AppendFormat("{0} ", b);
            Message message = new Message() { X = (short)position.X, Y = (short)position.Y, Type = eventType, TimeStamp = timestamp, WheelDelta = wheelDelta};
            //System.Diagnostics.Debug.WriteLine(String.Format("Sending: {0}: ", message));
            //System.Diagnostics.Debug.WriteLine(bytesString);
            _messenger.Send(message);   
        }

        public void SendKeyboardEvent(Event eventType, uint timestamp, byte vkKey)
        {

            //StringBuilder bytesString = new StringBuilder(255);
            //foreach (byte b in data)
            //   bytesString.AppendFormat("{0} ", b);
            bool success;
            byte macKey = MacVirtualKeyCodes.FromWindowsCode(vkKey, out success);
            
            if (!success)
                return;
            
            Message message = new Message() { Key = macKey, Type = eventType, TimeStamp = timestamp };
            //System.Diagnostics.Debug.WriteLine(String.Format("Sending: {0}: ", message));
            //System.Diagnostics.Debug.WriteLine(bytesString);
            _messenger.Send(message);
        }

        /*public void Test(IPAddress ip, int port)
        {
            _endPoint = new IPEndPoint(ip, port);

            for (int i = 0; i < 4; i++)
            {
                byte[] tempTime = new byte[4];

                uint ticks = (uint)(DateTime.Now.Ticks >> 32);//1 << (i * 8);

                tempTime = BitConverter.GetBytes(ticks);
                //Array.Reverse(tempTime);

                StringBuilder bytesString = new StringBuilder(255);

                foreach (byte b in tempTime)
                   bytesString.AppendFormat("{0} ", b);

                System.Diagnostics.Debug.WriteLine(String.Format("Sending: {1}: {0}", ticks, bytesString));

                _udp.Send(tempTime, tempTime.Length, _endPoint);
            }
        }*/

        public void Stop()
        {
            _mouseHook.Stop();
            _keyboardHook.Stop();
        }
    }
}
